<?php
/**
 * Created by PhpStorm.
 * User: Inhere
 * Date: 2015/1/29
 * Use : ...
 * File: Application.php
 */

namespace ulue\web;


use Ioc,Ulue,Debug;
use debug\ErrorHandler;

use ulue\base\providers;
use ulue\base\TheAppException;
use ulue\core\application\InterfaceWebApplication;
use ulue\core\application\AbstractWebApplication;
use ulue\core\environment\Environment;
use ulue\core\module\AbstractModule;
use ulue\core\module\ModuleManager;
use ulue\core\router\Router;
use ulue\core\ioc\Container;
use ulue\core\request\InterfaceRequest;
use ulue\core\request\Request;
use ulue\core\response\Response;
use ulue\core\response\InterfaceResponse;
use ulue\core\view\AbstractView;
use ulue\libs\helpers\ArrayHelper;
use ulue\libs\helpers\PhpHelper;
use ulue\web\Controller;
use ulue\web\Module;


class Application extends AbstractWebApplication implements InterfaceWebApplication
{

    /**
     * 设置后 全部请求都访问此路由
     * @example  'site/index'
     * @var string
     */
    protected $allMatch;

    /**
     * config['services']
     * @var array
     */
    // private $_serviceConf = [];

    /**
     * @param Container $di
     * @param InterfaceRequest $request
     * @param InterfaceResponse $response
     */
    public function __construct( array $config=[] , Container $di=null)
    {
        Debug::trace("i Application Run Initialization | 系统运行初始化开始，调试开始。\n Strat Time: ".$_SERVER['REQUEST_TIME_FLOAT']);

        $this->debug     = APP_DEBUG;

        $this->container = $di instanceof Container ? $di    : Ioc::build();

        Debug::start();
        ErrorHandler::register();

        Ulue::$app = $this;
        Ioc::$box  = Ulue::$di = $this->container;

        parent::__construct($config);

        // Set the execution datetime and timestamp;
        $this->params->def('execution.datetime' , date('Y-m-d H:i:s'));
        $this->params->def('execution.timestamp', $_SERVER['REQUEST_TIME_FLOAT']);
    }

//////////////////////////////////////////////// initialize ////////////////////////////////////////////////

    /**
     * initialize
     */
    protected function init()
    {
        parent::init();

        /**
         * @var $logger \Psr\Log\LoggerInterface;
         */
        $logger = $this->get('logger');

        $this->setLogger($logger);

        $this->defineBaseWebUrlAndConstant();

        // 启动 SESSION
        $this->get('session')->start();
    }


    /**
     * 设置 url 常量
     */
    private function defineBaseWebUrlAndConstant()
    {
        /**
         * @var $uri \ulue\core\router\Uri
         */
        $uri = $this->container->uri;

        /**
         * @var $request \ulue\core\request\Request
         */
        $req = $this->container->request;

        $hostUrl    = $req->rootUrl;
        $appUrl     = $req->rootUrl;
        $indexUrl   = $hostUrl . $req->scriptUrl;
        $currentUrl = $req->url;

        $staticUrl  = $appUrl.'/static';

        define('_HOST_'     , $hostUrl);
        define('_APP_'      , $appUrl);
        define('_INDEX_'    , $indexUrl);
        define('_STATIC_'   , $staticUrl);
        define('_CURRENT_'  , $currentUrl);

        $uri->set('host.path', $hostUrl)->alias('app.host')->alias('host')->alias('app.root')->alias('root.path');

        $uri->set('app.path', strcasecmp($hostUrl,$appUrl)===0 ? $appUrl : str_replace($hostUrl,'',$appUrl) )->alias('app');
        $uri->set('app.full', $appUrl);

        $uri->set(
            'index.path',
            str_replace($hostUrl,'',$indexUrl)
        )->alias('entry.path')->alias('app.entry')->alias('entry')->alias('app.index')->alias('index');

        $uri->set('index.full', $indexUrl)->alias('entry.full');

        $uri->set('static.path', '/static')->alias('asset.path');
        $uri->set('static.full', $staticUrl)->alias('asset.full');

        $uri->set('current.path', str_replace($hostUrl,'',$currentUrl))->alias('self.path');
        $uri->set('current.full', $currentUrl)->alias('self.full');

        $uri->set('home.url', '/');

        unset($uri, $hostUrl ,$appUrl ,$indexUrl ,$staticUrl);
    }

//////////////////////////////////////////////// execute ////////////////////////////////////////////////

    /**
     *
     */
    public function execute()
    {
        $this->beforeExecute();

        $this->triggerEvent(self::EVENT_BEFORE_EXECUTE, ['app' => $this] );

        $this->beginExecute();

        $this->triggerEvent(self::EVENT_AFTER_EXECUTE, ['app' => $this] );

        $this->afterExecute();

        $this->triggerEvent(self::EVENT_AFTER_RESPONSE, ['app' => $this] );

        // Send the application response.
        $this->respond();

        $this->triggerEvent(self::EVENT_BEFORE_RESPONSE, ['app' => $this] );

        Debug::trace("输出响应数据，调试结束，保存日志，应用运行完成。");

        Debug::end();

        // Save logs
        $this->logger->save();
    }

    /**
     * @throws TheAppException
     */
    protected function beginExecute()
    {
        $this->triggerEvent('onBeforeRouting', array('app' => $this));

        $this->route();

        $this->triggerEvent('onAfterRouting', array('app' => $this));

        $this->triggerEvent('onBeforeRender', array('app' => $this));

        $data = $this->getRendered();

        //
        if ( !($data instanceof Response) ) {
            $this->setBody($data);
        }

        $this->triggerEvent('onAfterRender', array('app' => $this));
    }

    /**
     * @throws TheAppException
     * @throws \DNotFoundException
     * @throws \DRequestException
     * @throws \DRuntimeException
     */
    protected function route()
    {
        Debug::trace("i 请求初始化、路由解析等处理完成，应用开始运行,载入模块，控制器...");

        if ( !$route = $this->allMatch ) {
            $route = $this->router->resolve();
        }

        /**
         * 1.开始解析 请求的路由字符串
         * @var $moduleMgr \ulue\core\module\moduleManager
         */
         // $moduleMgr = Ulue::$di->moduleMgr;

        $isPathRoute = is_string($route);
        $mRoute = $isPathRoute ? $route : $route['module'];

        // 2.激活应用模块控制实例.
        // 返回 去除已经激活模块路由的 余下路由path;
        // 若是 string 结构的 $route，则 $otherRoute 是余下的控制器以及动作方法的路由path;
        // 若是 array 结构的 $route, 返回的则用于检查 模块请求是否解析完整
        $otherRoute = $this->activeModule($mRoute);

        // $route is array, 检查 模块请求是否解析完整
        if (!$isPathRoute && $otherRoute) {
            throw new \DRuntimeException("无法解析请求，模块[{$this->module->id}]下不存在子模块[{$otherRoute}]");
        }

        $objModule = $this->module;
        $self = $this;

        // 将应用模块加入共享服务，可在整个应用运行周期内使用 Ioc::get('app.module') 获取实例
        // Ioc::share('module',function() use($self)
        // {
        //     return $self->module;
        // })->alias('application.module')->alias('app.module');

        // 3.得到控制器对象实例
        $cRoute = $isPathRoute ? $otherRoute : $route['controller'];
        $otherRoute = $objModule->activeController( $cRoute );

        $aRoute = $isPathRoute ? $otherRoute : $route['action'];
        $objController = $this->controller;
        $objController->activeAction( $aRoute );

// d($objModule,$this->router);
        // 将应用控制器加入共享服务，可在整个应用运行周期内使用 Ioc::get('app.controller') 获取实例
        // Ioc::share('controller',function()use($objController)
        // {
        //     return $objController;
        // })->alias('application.controller')->alias('app.controller');

        $this->defineRouteUrlConstant($this->router);

        unset($self, $mRroute, $objModule, $cRoute, $objController, $aRoute, $options);

        Debug::trace('应用各个部分载入解析完成。'.Debug::countTime());
    }

    /**
     * 获取渲染后的视图数据 并注册静态资源 设置视图缓存
     * @return string
     * @throws \DNotFoundException
     */
    protected function getRendered()
    {
        Debug::trace('i 获取渲染后的视图数据 并注册静态资源 设置视图缓存(若启用了)'.Debug::countTime());

        $ctrl = $this->controller;

        // 启用了缓存，
        if ( $enabled = Ioc::exists('staticCache') && !$this->debug ) {
            $key   = $ctrl->getId().$ctrl->getActionName();
            $cache = Ioc::get('staticCache');

            // 存在缓存直接返回
            if ($data = $cache->get($key)) {
                return $data;
            }
        }

        $output = $ctrl->execute();

        if ($output === null && $this->debug) {
            \Trigger::notice('页面是否是一片空白? 您是否是忘了返回渲染数据呐！:) !');
        }

        // 注册资源
        $asset = Ioc::get('asset');

        if ($this->debug) {
            $style  = 'dep/debug/css/debug.css';
            $script = 'dep/debug/js/debug.js';

            $asset->addCss($style)->addJs($script);
        }

        $output = $asset->autoInject($output);

        // 缓存数据
        if ($enabled) {
            $cache->set($key, $output);
        }

        $this->response->setHeader('X-Powered-By','ulueFramework', true);
        $this->response->setHeader('Test-Send-Header','successful', true);

        unset($cache,$ctrl,$options,$key, $enabled, $staticCache, $params);

        return $output;
    }

//////////////////////////////////////////////// attribute ////////////////////////////////////////////////

    /**
     * Sets the 设置后 全部请求都访问此路由.
     *
     * @param string $allMatch the all match
     * @return self
     */
    public function setAllMatch($allMatch)
    {
        if ( $allMatch && is_string($allMatch) ) {
            $this->allMatch = trim($allMatch);
        }

        return $this;
    }

    public function getUser()
    {
        return $this->container->getShared('user');
    }


//////////////////////////////////////////////// other ////////////////////////////////////////////////

    /**
     * 添加事件监听处理
     * @param $event obejct|string 事件名称
     * @param $listener callback 事件处理者
     * @return void
     */
    public function on($event, $listener, array $args=[])
    {
        $disp = $this->container->dispatcher;

        $disp->addEvent($event, $args)->addListener($listener, $event);
    }

    /**
     * 解除事件监听处理
     * @param $event
     * @param mixed $listener
     * @param bool $removeEvent 是否同时删除事件,默认仅是去除事件的监听器
     * @note  $removeEvent 若为 True，则不管 $removeListener 是否为True，都会去除此事件的监听器
     * @return bool
     */
    public function off($event, $listener = null, $removeEvent = false)
    {
        $disp = $this->container->dispatcher;

        // 去除对事件的所有监听器,同时删除事件信息
        if ( (bool)$removeEvent ) {
            $disp->clearListeners($event)->removeEvent($event);

            $result = ( $disp->hasEvent($event) && $disp->hasListeners($event) );

        // 仅去除事件的 $listener 监听器
        } elseif ($listener) {
            $result = $disp->removeListener($listener, $event)->hasListener($event);

        // 去除对事件的所有监听器
        } else {
            $result = $disp->clearListeners($event)->hasListeners($event);
        }

        unset($event,$disp);

        return $result;
    }

    /**
     * Trigger an event.如果有对应事件的监听者，监听者将会得到通知，在事件发生时做一些事情
     * @param   \ulue\core\event\InterfaceEvent|string $event The event object or name.
     * @param   array    $args  The arguments.
     * @return  \ulue\core\event\InterfaceEvent  The event after being passed through all listeners.
     */
    public function triggerEvent($event, array $args = array())
    {

        /** @var \ulue\core\event\Dispatcher $dispatcher */
        $dispatcher = $this->container->dispatcher;

        return $dispatcher->triggerEvent($event, $args);
    }

    public function __set($name, $value)
    {
        $method = 'set'.ucfirst( $name );

        if (method_exists($this, $method)) {
            $this->$method($value);
        } elseif ( substr($name, 0, 2) == 'on' && is_callable($value)) {
            $this->on( lcfirst(substr($name, 2)) , $value);
        } else {
            parent::__set($name, $value);
        }
    }

    /**
     * @param Router $router
     */
    protected function defineRouteUrlConstant()
    {
        Debug::trace('定义url常量！');

        $defaultModule = Ulue::$app->defaultModule;
        $routeArr     = $this->router->getRoute();
        $uri          = Ioc::get('app.uri');
        $rootUrl         = $uri->get('app.root');
        $entryUrl        = $uri->get('entry.full');

        $module     = $defaultModule== $routeArr['module'] ? '/' : '/'. $routeArr['module'] . '/' ;

        //rewrite_mode
        if ( (int) $this->router->format == 1) {
            if ( !PhpHelper::checkApacheRewriteMode() && APP_DEBUG) {
                \Trigger::notice('注意：Apache 的[ mod_rewrite ]没有开启无法使用 url rewrite 模式！');
            }

            $moduleUrl  = $rootUrl . $module;
        } else {
            $moduleUrl  = $entryUrl . $module;
        }

        $controllerUrl = $moduleUrl . $routeArr['controller'] . '/';
        $actionUrl     = $controllerUrl . $routeArr['action'];

        define('_MODULE_'    , $moduleUrl);
        define('_CONTROLLER_', $controllerUrl );
        define('_ACTION_'    , $actionUrl);

        $uri->set('module.path', str_replace($rootUrl,'',$moduleUrl))->alias('app.module')->alias('module');
        $uri->set('module.full', $moduleUrl );

        $uri->set('controller.path',
            str_replace($rootUrl,'',$controllerUrl)
            )->alias('app.controller')->alias('controller');
        $uri->set('controller.full', $controllerUrl );

        $uri->set('action.path', str_replace($rootUrl,'',$actionUrl))->alias('app.action')->alias('action');
        $uri->set('action.full', $actionUrl );

        unset($defaultModule, $routeArr, $uri, $router, $rootUrl, $entryUrl, $module);
    }


}// end class Application